#include "userprog/syscall.h"
#include "userprog/process.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "threads/init.h"
#include "filesys/file.h"
#include "filesys/filesys.h"
#include "threads/vaddr.h"
#include "filesys/filesys.h"
#include "devices/input.h"

char arg_num[]={0, 1, 1, 1, 2, 1, 1, 1, 3, 3, 2, 1, 1};
static void syscall_handler (struct intr_frame *);
static int get_user (const uint8_t *uaddr);
static bool put_user (uint8_t *udst, uint8_t byte);

/*User address access*/
static uint32_t read_word (const uint8_t* uaddr);
static uint8_t read_byte (const uint8_t* uaddr);
//static bool read_string (uint8_t * buffer, const uint8_t* uaddr, unsigned int len);
static bool verify_uaddr_string_r (const uint8_t* uaddr, unsigned int len);
static void verify_uaddr_string_w (uint8_t* uaddr, unsigned int len);
//static bool write_string (const uint8_t *src, uint8_t* uaddr_dst, unsigned int len);
static void write_byte (uint8_t* uaddr, uint8_t c);
//static void print_segfault(void);

/*Syscall function*/
static void halt(void);
static void exit_(int arg_0);
static int exec(int arg_0);
static int wait(int arg_0);
static int create (int arg_0, int arg_1);
static bool remove (int arg_0);
static int open (int arg_0);
static int filesize (int arg_0);
static int read (int arg_0, int arg_1, int arg_2);
static int write (int arg_0, int arg_1, int arg_2);
static void seek (int arg_0, int arg_1);
static unsigned tell (int arg_0);
static void close (int arg_0);
#ifdef _TEST_
static void print_frame_usage(void);
#endif

void
syscall_init (void) 
{
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}
//return value to eax
static void
syscall_handler (struct intr_frame *f) 
{
#ifdef _TEST_
	if(read_word(f->esp) == 0x33)
	{
		print_frame_usage();
		return;
	}
#endif
	thread_current()->syscall = true;
	thread_current()->esp = f->esp;
	int syscall_num = read_word(f->esp);
	int arg[3];
	int i;
	if(syscall_num < 0 || (unsigned)syscall_num > (sizeof arg_num))
		exit_(-1);
	for(i = 0; i < arg_num[syscall_num]; i++){
		arg[i] = read_word((uint8_t*)(((uint32_t*)(f->esp))+i+1));
	}
	switch(syscall_num)
	{
		case SYS_HALT:
			{
				halt();
				break;
			}
		case SYS_EXIT:
			{
				exit_(arg[0]);
				break;
			}
		case SYS_EXEC:
			{
				f->eax = exec(arg[0]);
				break;
			}
		case SYS_WAIT:
			{
				f->eax = wait (arg[0]);
				break;
			}
		case SYS_CREATE:
			{
				f->eax = create (arg[0], arg[1]);
				break;
			}
		case SYS_REMOVE:
			{
				f->eax = remove (arg[0]);
				break;
			}
		case SYS_OPEN:
			{
				f->eax = open (arg[0]);
				break;
			}
		case SYS_FILESIZE:
			{
				f->eax = filesize(arg[0]);
				break;
			}
		case SYS_READ:
			{
				f->eax = read(arg[0], arg[1], arg[2]);
				break;
			}
		case SYS_WRITE:
			{
				f->eax = write (arg[0], arg[1], arg[2]);
				break;
			}
		case SYS_SEEK:
			{
				seek (arg[0], arg[1]);
				break;
			}
		case SYS_TELL:
			{
				f->eax = tell (arg[0]);
				break;
			}
		case SYS_CLOSE:
			{
				close(arg[0]);
				break;
			}
	}	

	thread_current()->syscall = false;
}
//for debug 
/*
static void print_segfault(void)
{
	printf("Segmentation fault\n");
}
*/
/* Reads a byte at user virtual address UADDR.
 * UADDR must be below PHYS_BASE.
 * Returns the byte value if successful, -1 if a segfault
 * occurred. */
static int
get_user (const uint8_t *uaddr)
{
	int result;
	//$1f = eip
	asm ("movl $1f, %0; movzbl %1, %0; 1:"
			: "=&a" (result) : "m" (*uaddr));
	return result;
}
/* Writes BYTE to user address UDST.
 * UDST must be below PHYS_BASE.
 * Returns true if successful, false if a segfault occurred. */
static bool
put_user (uint8_t *udst, uint8_t byte)
{
	int error_code;
	asm ("movl $1f, %0; movb %b2, %1; 1:"
			: "=&a" (error_code), "=m" (*udst) : "r" (byte));
	return error_code != -1;
}
static uint32_t
read_word (const uint8_t* uaddr)
{
	//read 4byte uaddr, uaddr+1, uaddr+2, uaddr+3
	int c;
	if(uaddr+3 >= (uint8_t*) PHYS_BASE || (c = get_user(uaddr))== -1)
	{
		//print_segfault();
		exit_(-1);
	}
	return *((uint32_t*)uaddr);
}

static void halt(void)
{
	power_off();
	NOT_REACHED();
}
static void exit_(int arg_0)
{
	thread_current()->exitcode = arg_0;
	thread_exit();
	NOT_REACHED();
}
static int exec(int arg_0)
{
	if((const uint8_t*)arg_0 == NULL)
		return -1;
	verify_uaddr_string_r ((const uint8_t*)arg_0, 15);
	return (int)process_execute ((const char*)arg_0);
}
static int wait(int arg_0)
{
	/*
		 verify pid
	 */
	struct thread* t = thread_p((tid_t)arg_0);
	if(t == NULL)
		exit_(-1);
	/* If t is in user vaddr then error! */
	if(&t->magic < (unsigned *)PHYS_BASE)
		exit_(-1);
	/* If thread t is bad or already terminated */
	if(get_user((uint8_t*)&t->magic) == -1)
		exit_(-1);
	/* If thread t is bad*/
	if(!check_thread_magic(t))
		exit_(-1);
	return process_wait((tid_t)arg_0);
}
static int create (int arg_0, int arg_1)
{
	const char* file_name = (const char*)arg_0;
	unsigned initial_size = (unsigned) arg_1;
	verify_uaddr_string_r ((const uint8_t*)file_name, 15);
	return filesys_create (file_name, initial_size);
}

static bool remove (int arg_0)
{
	const char *file = (const char *) arg_0;
	if(file == NULL)
		return false;
	verify_uaddr_string_r ((const uint8_t*)file, 15);
	return filesys_remove (file);
}

static int open (int arg_0)
{
	const char* file_name = (const char*)arg_0;
	if(file_name == NULL)
		return -1;
	verify_uaddr_string_r ((const uint8_t*)file_name, 15);
	struct file* file = filesys_open(file_name);
	struct thread* cur = thread_current();
	int fd;
	if(file == NULL)
		return -1;
	for(fd = 3 ; fd < FILE_MAX; fd++)
	{
		if(cur->u_open_files[fd] == NULL)
		{
			cur->u_open_files[fd] = file;
			return fd;
		}
	}
	return -1; //Too many files opened
}

static int filesize (int arg_0)
{
	int fd = arg_0;
	if(fd < 0 || fd >= FILE_MAX)
		return -1;

	struct thread *cur = thread_current ();
	struct file *file = cur->u_open_files[fd];
	if(file == NULL)
		return -1;

	return (file == NULL) ? -1 : file_length (file);
}

static int read (int arg_0, int arg_1, int arg_2)
{
	int fd = arg_0;
	if(fd < 0 || fd >= FILE_MAX)
		return -1;
	void *buffer = (void *) arg_1;
	unsigned size = (unsigned) arg_2;
	if(buffer == NULL)
		return -1;
	verify_uaddr_string_w (buffer, size);
	/* read input from keyboard */
	if (fd == STDIN_FILENO)
	{
		unsigned int numch = 0; /* number of characters read so far */

		while (numch < size)
		{
			uint8_t c = input_getc ();

			//printf("input_getc() : %c,%d\n",c,c);

			if (c == 10) /* newline  */
			{
				((char *) buffer)[numch++] = '\r';
				break;
			}
			((char *) buffer)[numch++] = c;
		}

		return numch;
	}
	else if (fd == STDOUT_FILENO)
	{
		return -1;
	}
	else
	{
		struct thread *cur = thread_current ();
		struct file *file = cur->u_open_files[fd];
		return (file == NULL) ? -1 : file_read (file, buffer, size); 
	}
}

static int write (int arg_0, int arg_1, int arg_2)
{
	int fd = arg_0;
	if(fd < 0 || fd >= FILE_MAX)
		return -1;
	const void *buffer = (const void *)arg_1;
	unsigned size = (unsigned) arg_2;
	if(buffer == NULL)
		return -1;

	verify_uaddr_string_r (buffer, size);
	if (fd >= 2)
	{
		struct thread *cur = thread_current ();
		struct file *file = cur->u_open_files[fd];

		if (file == NULL)
		{
			return -1;
		}
		else
		{
			int written = file_write (file, buffer, size);

			return written;
		}
	}
	else if (fd == STDOUT_FILENO)
	{
		putbuf (buffer, size);
		return size;
	}
	else if (fd <= STDIN_FILENO)
	{
		return -1; 
	}
	return size;
}

static void seek (int arg_0, int arg_1)
{
	int fd = arg_0;
	if(fd < 0 || fd >= FILE_MAX)
		return ;
	unsigned position = (unsigned) arg_1;

	struct thread *cur = thread_current ();
	struct file *file = cur->u_open_files[fd];
	if(file == NULL)
		return ;

	file_seek (file, position);
}

static unsigned tell (int arg_0)
{
	int fd = arg_0;
	if(fd < 0 || fd >= FILE_MAX)
		return -1;
	
	struct thread *cur = thread_current ();
	struct file *file = cur->u_open_files[fd];
	
	if(file == NULL)
		return -1;

	return file_tell (file);
}

static void close (int arg_0)
{
	int fd = arg_0;
	if(fd < 3 || fd >= FILE_MAX)
		return ;
	struct thread *cur = thread_current ();
	struct file *file = cur->u_open_files[fd];
	if(file == NULL)
		return ;

	file_close (file);
	cur->u_open_files[fd] = NULL;
}
static uint8_t read_byte (const uint8_t* uaddr)
{
	int c;
	if(uaddr >= (uint8_t*)PHYS_BASE)
	{
	//	print_segfault();
		exit_(-1);
	}
	if((c = get_user(uaddr)) == -1)
	{
	//	print_segfault();
		exit_(-1);
	}
	return (uint8_t)c;
}
static void write_byte (uint8_t* uaddr, uint8_t c)
{
	if(uaddr >= (uint8_t*)PHYS_BASE)
	{
	//	print_segfault();
		exit_(-1);
	}
	if(!put_user(uaddr, c))
	{
	//	print_segfault();
		exit_(-1);
	}
}
/*
If you read bytes from user address, then you must verify that address is in user virtual memory
	 */
static bool verify_uaddr_string_r (const uint8_t* uaddr, unsigned int len)
{
	uint8_t c;
	while((c = read_byte(uaddr)) != '\0' && len != 0)
	{
		uaddr++;
		len --;
	}
	if(c != '\0' && len == 0)
		return false;
	return true;
}
/*
If you write bytes to user address, then you must verify that address is in user virtual memory
	 */
static void verify_uaddr_string_w (uint8_t* uaddr, unsigned int len)
{
	while(len != 0)
	{
		write_byte (uaddr++, '\0');
		len --;
	}
}


#ifdef _TEST_
static void print_frame_usage(void)
{
	extern void print_frame_usage_(void);
	print_frame_usage_();
}
#endif
